package com.fourforfo.fourmall.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.fourforfo.common.to.es.SkuEsModel;
import com.fourforfo.fourmall.search.config.FourmallElasticSearchConfig;
import com.fourforfo.fourmall.search.constant.EsConstant;
import com.fourforfo.fourmall.search.service.MallSearchService;
import com.fourforfo.fourmall.search.vo.SearchParam;
import com.fourforfo.fourmall.search.vo.SearchResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.aggregations.Aggregation;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.aggregations.bucket.terms.TermsAggregationBuilder;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.*;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * 搜索服务的Service
 */
@Slf4j
@Service
public class MallSearchServiceImpl implements MallSearchService {
    /**
     * 操作elasticSearch
     */
    @Autowired
    private RestHighLevelClient client;


    /**
     * 根据前台的检索参数查询相应数据
     * @param param 检索的所有参数
     * @return 返回检索的结果
     */
    @Override
    public SearchResult search(SearchParam param) {
        //1、动态构建出查询需要的DSL语句
        SearchResult result = null;

        //1.1、准备检索请求
        SearchRequest searchRequest = buildSearchRequest(param);

        try {
            //1.2 执行检索请求
            SearchResponse response = client.search(searchRequest, FourmallElasticSearchConfig.COMMON_OPTIONS);

            //1.3 分析响应数据并封装成我们需要的格式
            result = buildSearchResult(response,param);

        } catch (IOException ioException) {
            ioException.printStackTrace();
        }

        return result;
    }


    /**
     *  构建检索结果
     * @param response 从elasticSearch返回的相应数据
     * @param param 页码
     * @return
     */
    private SearchResult buildSearchResult(SearchResponse response, SearchParam param) {
        SearchResult result = new SearchResult();

        //1.返回的所有查询到的商品
        SearchHit[] hits = response.getHits().getHits();
        List<SkuEsModel> products = new ArrayList<>();
        if(hits!=null && hits.length>0){
            for (SearchHit hit : hits) {
                String sourceAsString = hit.getSourceAsString();
                SkuEsModel skuEsModel = JSON.parseObject(sourceAsString,SkuEsModel.class);
                //设置一下高亮属性
                if(hit.getHighlightFields()!=null&&hit.getHighlightFields().size()>0){
                    HighlightField highlightField = hit.getHighlightFields().get("skuTitle");
                    String skuTitle = highlightField.getFragments()[0].string();
                    skuEsModel.setSkuTitle(skuTitle);
                }

                products.add(skuEsModel);
            }
        }
        result.setProducts(products);

        //==================属性，品牌，分类信息从聚合信息获取====================
        Aggregations aggregations = response.getAggregations();
        //2.当前所有商品涉及到的所有属性信息
        List<SearchResult.AttrVO> attrVOS = new ArrayList<>();
        ParsedNested attr_agg = aggregations.get("attr_agg");
        ParsedLongTerms attr_id_agg = attr_agg.getAggregations().get("attr_id_agg");
        Map<Long,String> attrNameMap = new HashMap<>();
        for (Terms.Bucket bucket : attr_id_agg.getBuckets()) {
            SearchResult.AttrVO attrVO = new SearchResult.AttrVO();
            //2.1 设置属性id
            Long attrId = bucket.getKeyAsNumber().longValue();
            attrVO.setAttrId(attrId);
            //2.2 设置属性名
            ParsedStringTerms attr_name_agg = bucket.getAggregations().get("attr_name_agg");
            String attrName = attr_name_agg.getBuckets().get(0).getKeyAsString();
            attrVO.setAttrName(attrName);
            attrNameMap.put(attrId,attrName);//用于面包屑功能的实现

            //2.3 设置属性value
            ParsedStringTerms attr_value_agg = bucket.getAggregations().get("attr_value_agg");
            List<String> attrValues = new ArrayList<>();
            for (Terms.Bucket attr_value_aggBucket : attr_value_agg.getBuckets()) {
                attrValues.add(attr_value_aggBucket.getKeyAsString());
            }
            attrVO.setAttrValue(attrValues);

            attrVOS.add(attrVO);
        }
        result.setAttrs(attrVOS);

        //3.所有品牌信息
        List<SearchResult.BrandVO> brandVOS = new ArrayList<>();
        ParsedLongTerms brand_agg = aggregations.get("brand_agg");
        List<? extends Terms.Bucket> brandBuckets = brand_agg.getBuckets();
        for (Terms.Bucket bucket : brandBuckets) {
            SearchResult.BrandVO brandVO = new SearchResult.BrandVO();
            //3.1 brand_agg是通过brand_id来聚合的，可以通过bucket获取key
            Long brandId = bucket.getKeyAsNumber().longValue();
            brandVO.setBrandId(brandId);
            //3.2 获取子聚合brand_name_agg,从brand_name_agg中获取name
            ParsedStringTerms  brand_name_agg = bucket.getAggregations().get("brand_name_agg");
            String brandName = brand_name_agg.getBuckets().get(0).getKeyAsString();
            brandVO.setBrandName(brandName);

            //将brandId和brandName数据放入map

            //3.3 获取子聚合brand_img_agg，从brand_img_agg中获取img
            ParsedStringTerms  brand_img_agg = bucket.getAggregations().get("brand_img_agg");
            String brandImg = brand_img_agg.getBuckets().get(0).getKeyAsString();
            brandVO.setBrandImg(brandImg);

            brandVOS.add(brandVO);
        }
        result.setBrands(brandVOS);

        //4.所有分类信息
        List<SearchResult.CatalogVO> catalogVOS = new ArrayList<>();
        ParsedLongTerms catalog_agg = aggregations.get("catalog_agg");
        List<? extends Terms.Bucket> catalogBuckets = catalog_agg.getBuckets();
        for (Terms.Bucket bucket : catalogBuckets) {
            SearchResult.CatalogVO catalogVO = new SearchResult.CatalogVO();
            //4.1 获取分类id
            catalogVO.setCatalogId(bucket.getKeyAsNumber().longValue());

            //4.2 获取分类名字
            ParsedStringTerms catalog_name_agg = bucket.getAggregations().get("catalog_name_agg");
            String catalogName = catalog_name_agg.getBuckets().get(0).getKeyAsString();
            catalogVO.setCatalogName(catalogName);

            catalogVOS.add(catalogVO);
        }

        result.setCatalogs(catalogVOS);

        //==================分页信息，获取总记录数后可通过pageNum计算====================
        //5.分页信息-页码
        result.setPageNum(param.getPageNum());

        //5.分页信息-总记录数
        long total = response.getHits().getTotalHits().value;
        result.setTotal(total);

        //5.分页信息-总页码
        int pageSize = EsConstant.PRODUCT_PAGESIZE;
        int totalPages = (int) (total%pageSize == 0 ? total/pageSize:(total/pageSize+1));
        result.setTotalPages(totalPages);

        //6. 构建面包屑导航功能
        List<SearchResult.NavVO> navVOS = new ArrayList<>();

        if(param.getAttrs()!=null&&param.getAttrs().size()>0){
            String attrUrlRegex = "[0-9]+_.+[:?.+]*";
            navVOS = param.getAttrs().stream().filter(attr->{
                if(Pattern.matches(attrUrlRegex,attr)){
                    //分析每个attrs传过来的查询参数值
                    return StringUtils.isEmpty(attr)?false:true;
                }else{//不匹配说明格式错误
                    log.warn("部分attr格式错误，将在面包屑导航中排除此attr信息！attr："+attr);
                    return false;
                }
            }).map(attr->{
                //分析每个attrs传过来的查询参数值
                SearchResult.NavVO navVO = new SearchResult.NavVO();
                String[] s = attr.split("_");
                navVO.setNavValue(s[1]);
                String attrName = attrNameMap.get(Long.parseLong(s[0]));
                navVO.setNavName(attrName);
                //取消了这个面包屑以后，我们要跳转到那个地方，将请求地址的url里面的当前置空
                //拿到所有的查询条件，去掉当前
                String queryString = param.getQueryString();
                if(!StringUtils.isEmpty(queryString)&&!StringUtils.isEmpty(attr)){
                    String encode = null;
                    try {
                        encode = URLEncoder.encode(attr,"UTF-8");
                        encode = encode.replace("+","%20");
                    } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                    }
                    String queryReplaceRegex = "&?(attrs="+encode+")";

                    queryString = queryString.replaceFirst(queryReplaceRegex,"");
                    if(StringUtils.isEmpty(queryString)){
                        navVO.setLink("http://search.fourmall.com/list.html");
                    }else{
                        navVO.setLink("http://search.fourmall.com/list.html?"+queryString);
                    }

                }
                return navVO;
            }).collect(Collectors.toList());
            result.setNavs(navVOS);
        }

        return result;
    }

    /**
     * 构建检索请求
     * 模糊匹配，过滤（属性，分类，品牌，价格区间，库存），排序，分页，高亮，聚合分析
     * @param param
     * @return
     */
    private SearchRequest buildSearchRequest(SearchParam param) {
        //新建builder构建DSL语句
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        /**
         * 1. 查询条件：模糊匹配，过滤（属性，分类，品牌，价格区间，库存）
         */
        //boot-query
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //1.1 must模糊匹配skuTitle
        if(!StringUtils.isEmpty(param.getKeyword())){
            boolQueryBuilder.must(QueryBuilders.matchQuery("skuTitle",param.getKeyword()));
        }
        //1.2 filter过滤（属性，分类，品牌，价格区间，库存）
        List<QueryBuilder> filter = boolQueryBuilder.filter();
        //1.2 bool-filter,三级分类id
        if(param.getCatalog3Id()!=null){
            filter.add(QueryBuilders.termQuery("catalogId",param.getCatalog3Id()));
        }
        //1.2 bool-filter,品牌id
        if(param.getBrandIds()!=null&&param.getBrandIds().size()>0){
            filter.add(QueryBuilders.termsQuery("brandId",param.getBrandIds()));
        }
        //1.2 bool-filter，库存
        if(param.getHasStock()!=null){
            //只有hasStock为1才查有库存的，等于0或其他数字则不管是否有库存都查
            filter.add(QueryBuilders.termQuery("hasStock",param.getHasStock()==1));
        }

        //1.2 bool-filter 价格区间 _500,0_500,500_
        /*
        {
          "range": {
            "skuPrice": {
              "gte": 4000,
              "lte": 5000
            }
          }
        }
         */
        String skuPrice = param.getSkuPrice();
        if(!StringUtils.isEmpty(skuPrice)&&skuPrice.length()>1){
            String priceRegex = "([0-9]*_[0-9]+)|([0-9]+_[0-9]*)";//匹配 _500,500_,0_500
            if(Pattern.matches(priceRegex,skuPrice)){
                RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("skuPrice");
                String[] skuPrices = skuPrice.split("_");
                if(skuPrice.startsWith("_")){//格式为 _500,会分割成两个字符串""和"500"
                    rangeQuery.lte(skuPrices[1]);
                }else if(skuPrice.endsWith("_")){//格式为 500_,会分割成一个字符串"500"
                    rangeQuery.gte(skuPrices[0]);
                }else{ //格式为 0_500 ,会分割成两个字符串"0"和"500"
                    rangeQuery.gte(skuPrices[0]).lte(skuPrices[1]);
                }
                filter.add(rangeQuery);
            }else{
                log.warn("skuPrice价格区间格式输入错误，已排除该查询条件！");
            }
        }

        /**
         * 1.2 bool-filter-nested-bool 属性 在elasticSearch中保存为nested类型
         * attrs的url格式为：attrs=1_5寸:8寸&attrs=2_16G:8G，即attrs=[attrId]_[attr_value:attr_value];
         */
        if(param.getAttrs()!=null&&param.getAttrs().size()>0){
            //匹配 [attrId]_[attr_value:attr_value];attr_value可以为1或多个，attrId有一个
            String attrUrlRegex = "[0-9]+_.+[:?.+]*";
            //只将其中格式正确的attr作为查询条件
            for(String attr : param.getAttrs()){
                // attr=1_5寸 或 attr=2_16G:8G
                if(Pattern.matches(attrUrlRegex,attr)){
                    BoolQueryBuilder nestedBoolQuery = QueryBuilders.boolQuery();
                    String[] s = attr.split("_",2);//2_16G:8G分割后为 s[0]=2,s[1]=16G:8G
                    String attrId = s[0];
                    String[] attrValues = s[1].split(":");

                    nestedBoolQuery.must(QueryBuilders.termQuery("attrs.attrId",attrId));
                    nestedBoolQuery.must(QueryBuilders.termsQuery("attrs.attrValue",attrValues));
                    NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs", nestedBoolQuery, ScoreMode.None);
                    filter.add(nestedQuery);
                }else{
                    log.warn("有attr的格式不对，已排除该attr作为查询条件");
                }
            }

        }
        sourceBuilder.query(boolQueryBuilder);

        /**
         * 排序,分页,高亮
         */
        //2.1 排序
        /*
            sort = skuPrice_desc/asc
            sort = saleCount_desc/asc
            sort = hotScore_desc/asc
         */
        String sortString = param.getSort();
        String sortStrRegex = "(saleCount|hotScore|skuPrice)_(desc|asc)";
        if(!StringUtils.isEmpty(sortString)){
            if(Pattern.matches(sortStrRegex,sortString)){
                String[] s = sortString.split("_");
                sourceBuilder.sort(s[0],"desc".equals(s[1])?SortOrder.DESC:SortOrder.ASC);
            }else{
                log.warn("sort属性的格式错误,将不会进行排序操作,格式应该为[字段名]_desc或[字段名]_asc");
            }
        }
        //2.2 分页 from = (pageNum-1) * size;
        Integer pageNum = param.getPageNum();
        if(pageNum!=null){
            sourceBuilder.from((pageNum-1)*EsConstant.PRODUCT_PAGESIZE);
            sourceBuilder.size(EsConstant.PRODUCT_PAGESIZE);
        }
        //2.2 高亮
        if(!StringUtils.isEmpty(param.getKeyword())){
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("skuTitle");
            highlightBuilder.preTags("<b style='color:red'>");
            highlightBuilder.postTags("</b>");
            sourceBuilder.highlighter(highlightBuilder);
        }

        /**
         * 聚合分析
         */
        //1. 品牌聚合
        TermsAggregationBuilder brand_agg = AggregationBuilders.terms("brand_agg");
        brand_agg.field("brandId").size(50);
        //1.1 品牌聚合的子聚合
        brand_agg.subAggregation(AggregationBuilders.terms("brand_name_agg").field("brandName").size(1));
        brand_agg.subAggregation(AggregationBuilders.terms("brand_img_agg").field("brandImg").size(1));
        sourceBuilder.aggregation(brand_agg);

        //2. 分类聚合
        TermsAggregationBuilder catalog_agg = AggregationBuilders.terms("catalog_agg");
        catalog_agg.field("catalogId").size(20);
        //2.1 分类聚合的子聚合
        catalog_agg.subAggregation(AggregationBuilders.terms("catalog_name_agg").field("catalogName").size(1));
        sourceBuilder.aggregation(catalog_agg);

        //3. 属性attr聚合
        NestedAggregationBuilder attr_agg = AggregationBuilders.nested("attr_agg", "attrs");

        //3.1 attr_id_agg聚合包含两个子聚合 attr_name_agg 和 attr_value_agg
        TermsAggregationBuilder attr_id_agg = AggregationBuilders.terms("attr_id_agg").field("attrs.attrId").size(10);
        //3.1 当前attr_id对应的名字
        attr_id_agg.subAggregation(AggregationBuilders.terms("attr_name_agg").field("attrs.attrName").size(1));
        //3.1 当前attr_id对应的值
        attr_id_agg.subAggregation(AggregationBuilders.terms("attr_value_agg").field("attrs.attrValue").size(50));

        //3.2 attr_agg包含子聚合attr_id_agg
        attr_agg.subAggregation(attr_id_agg);
        sourceBuilder.aggregation(attr_agg);
        log.info("构建的DSL："+sourceBuilder.toString());

        SearchRequest searchRequest = new SearchRequest(new String[]{EsConstant.PRODUCT_INDEX},sourceBuilder);

        return searchRequest;

    }
}
